﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
using System.Data.Common;

namespace Proyecta.Database
{
    /// <summary>
    /// Permite realizar INSERT, UPDATE y DELETE a una tabla especifica en
    /// la base de datos.
    /// </summary>
    public class DatabaseHelper : Helper
    {
        /// <summary>
        /// Crea un nuevo objeto DatabaseHelper.
        /// </summary>
        public DatabaseHelper() : base() { }

        /// <summary>
        /// Inserta el objeto BO en la tabla especificada dentro de la base de datos.
        /// </summary>
        /// <param name="bo">Instancia del objeto BO a insertar.</param>
        /// <param name="tabla">Tabla en la base de datos donde se insertara el objeto.</param>
        /// <param name="campo1">Propiedad que funge como identificador del BO (ID)</param>
        public void Insert(object bo, string tabla, string campoID)
        {
            try
            {
                if (bo == null) throw new Exception("Objeto BO invalido.");
                if (!IsValidObjectName(tabla)) throw new Exception("El nombre de la tabla no es valido");
                if (!IsValidObjectName(campoID)) throw new Exception("El nombre del campo ID no es valido");

                string sql = "INSERT INTO " + tabla + "({0}) VALUES ({1})";
                bool existeColumna = false;
                List<string> campos = new List<string>();
                List<string> valores = new List<string>();

                Command.Parameters.Clear();
                PropertyInfo[] props = bo.GetType().GetProperties();
                PropertyInfo key = null;
                foreach (PropertyInfo p in props)
                {
                    object val = p.GetValue(bo, null);

                    if (!string.IsNullOrWhiteSpace(campoID) && p.Name.ToLower() == campoID.ToLower())
                    {
                        if (
                            (val is string && string.IsNullOrWhiteSpace(val.ToString())) ||
                            (val is Int32 && (Int32)val == 0) ||
                            (val is Int64 && (Int64)val == 0) ||
                            (val is UInt32 && (UInt32)val == 0) ||
                            (val is UInt64 && (UInt64)val == 0)
                        )
                        {
                            key = p;
                            existeColumna |= true;
                            continue;
                        }
                    }

                    campos.Add(p.Name);
                    valores.Add("@" + p.Name);

                    PrepareParameter(p.Name, val);
                    existeColumna |= (p.Name.ToLower() == campoID.ToLower());
                }

                // Si se especifico el campo clave y existe la columna entonces arrojar error.
                if (!string.IsNullOrWhiteSpace(campoID) && !existeColumna) throw new Exception("La propiedad " + campoID + " no existe dentro del BO.");

                // Ejecutar INSERT
                Command.CommandText = string.Format(sql, string.Join(", ", campos.ToArray()), string.Join(", ", valores));
                Command.ExecuteNonQuery();

                // Si se especifico el campo clave entonces...
                if (!string.IsNullOrWhiteSpace(campoID))
                {
                    // Recuperar el ID.
                    Command.CommandText = "SELECT @@IDENTITY";
                    object id = Command.ExecuteScalar();

                    if (key != null)
                    {
                        // Poner el ID nuevo en la propiedad correspondiente.
                        key.SetValue(bo, Convert.ChangeType(id, key.PropertyType), null);
                    }
                }
            }
            catch
            {
                Rollback();
                throw;
            }
        }

        /// <summary>
        /// Inserta los objetos BO en la tabla especificada dentro de la base de datos.
        /// </summary>
        /// <param name="bo">Arreglo de instancias de objetos BO a insertar.</param>
        /// <param name="tabla">Tabla en la base de datos donde se insertara el objeto.</param>
        /// <param name="campoID">Propiedad que funge como identificador del BO (ID)</param>
        public void Insert(object[] bo, string tabla, string campoID)
        {
            if (bo == null) throw new Exception("Lista de objetos BO invalida.");
            foreach (object b in bo)
                this.Insert(b, tabla, campoID);
        }

        /// <summary>
        /// Actualiza el objeto BO en la tabla especificada dentro de la base de datos.
        /// </summary>
        /// <param name="bo">Instancia del objeto BO a insertar.</param>
        /// <param name="tabla">Tabla en la base de datos donde se insertara el objeto.</param>
        /// <param name="campoID">Propiedad que funge como identificador del BO (ID)</param>
        public void Update(object bo, string tabla, string campoID)
        {
            try
            {
                if (!IsValidObjectName(tabla)) throw new Exception("El nombre de la tabla no es valido");
                if (!IsValidObjectName(campoID)) throw new Exception("El nombre del campo ID no es valido");

                string sql = "UPDATE " + tabla + " SET {0} WHERE {1} = {2}";
                List<string> campos = new List<string>();
                bool existeColumna = false;

                Command.Parameters.Clear();
                PropertyInfo[] props = bo.GetType().GetProperties();
                foreach (PropertyInfo p in props)
                {
                    PrepareParameter(p.Name, p.GetValue(bo, null));
                    if (p.Name.ToLower() == campoID.ToLower())
                    {
                        existeColumna |= true;
                        continue;
                    }
                    campos.Add(p.Name + " = @" + p.Name);
                    existeColumna |= (p.Name.ToLower() == campoID.ToLower());
                }

                if (!existeColumna) throw new Exception("La propiedad " + campoID + " no existe dentro del BO.");

                Command.CommandText = string.Format(sql, string.Join(", ", campos), campoID, "@" + campoID);
                Command.ExecuteNonQuery();
            }
            catch
            {
                Rollback();
                throw;
            }
        }

        /// <summary>
        /// Actualiza el objeto BO en la tabla especificada dentro de la base de datos.
        /// </summary>
        /// <param name="id">Instancia del objeto BO a insertar.</param>
        /// <param name="tabla">Tabla en la base de datos donde se insertara el objeto.</param>
        /// <param name="campoID">Propiedad que funge como identificador del BO (ID)</param>
        public void Delete(object id, string tabla, string campoID)
        {
            try
            {
                if (!IsValidObjectName(tabla)) throw new Exception("El nombre de la tabla no es valido");
                if (!IsValidObjectName(campoID)) throw new Exception("El nombre del campo ID no es valido");

                string sql = "DELETE FROM " + tabla + " WHERE {0} = {1}";

                Command.Parameters.Clear();
                PrepareParameter(campoID, id);

                Command.CommandText = string.Format(sql, campoID, "@" + campoID);
                Command.ExecuteNonQuery();
            }
            catch
            {
                Rollback();
                throw;
            }
        }
    }
}